package com.tianji.remark.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.remark.constants.RedisConstants;
import com.tianji.remark.entity.dto.LikeRecordFormDTO;
import com.tianji.remark.entity.dto.LikedTimesDTO;
import com.tianji.remark.entity.po.LikedRecord;
import com.tianji.remark.mapper.LikedRecordMapper;
import com.tianji.remark.service.ILikedRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * <p>
 * 点赞记录表 服务实现类
 * </p>
 *
 * @author lemon
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LikedRecordRedisServiceImpl extends ServiceImpl<LikedRecordMapper, LikedRecord> implements ILikedRecordService {

    private final RabbitMqHelper mqHelper;

    private final StringRedisTemplate stringRedisTemplate;


    /**
     * 新增取消点赞记录
     *
     * @param dto
     */
    @Override
    public void saveLikedRecord(LikeRecordFormDTO dto) {
        Long userId = UserContext.getUser();
        Long bizId = dto.getBizId();
        if (userId == null || bizId == null) {
            throw new IllegalArgumentException("参数错误");
        }
        // 是否点赞或者取消点赞成功
        boolean flag = true;
        if (dto.getLiked()) {
            // 点赞
            flag = liked(dto, userId);
        } else {
            // 取消点赞
            flag = unliked(dto, userId);
        }
        if (!flag) {
            // 说明点赞或者取消点赞失败
            // 就是已经点过赞了
            return;
        }

        // 统计该业务下的总的点赞数
        // 先存入redis,后面使用定时任务同步到数据库
        // 基于redis统计id的总点赞数量
        String key = RedisConstants.LIKE_BIZ_KEY_PREFIX + dto.getBizId();
        Long totalLikedNum = stringRedisTemplate.opsForSet().size(key);
        if (totalLikedNum == null) {
            return;
        }

        // 缓存到redis
        // 使用zset结构
        String bizTypeTotalLikedNumKey = RedisConstants.LIKE_COUNT_KEY_PREFIX + dto.getBizType();
        stringRedisTemplate.opsForZSet().add(bizTypeTotalLikedNumKey, String.valueOf(dto.getBizId()), totalLikedNum);

    }

    /**
     * 批量查询点赞状态  redis的for循环
     *
     * @param bizIds
     * @return
     */
    @Override
    public Set<Long> getLikesStatus(List<Long> bizIds) {
        if (CollUtils.isEmpty(bizIds)) {
            return CollUtils.emptySet();
        }
        // 1.获取当前用户
        Long userId = UserContext.getUser();

        // 因为都存在redis中，所以直接从redis中获取
        // 首先是使用原始的方式
        String keyPrefix = RedisConstants.LIKE_BIZ_KEY_PREFIX;

        Set<Long> collect = new HashSet<>();
        for (Long bizId : bizIds) {
            String key = keyPrefix + bizId;
            // 判断是否存在
            Boolean member = stringRedisTemplate.opsForSet().isMember(key, String.valueOf(userId));
            if (member != null && member) {
                collect.add(bizId);
            }
        }
        return collect;
    }

    /**
     * 使用redis的pipeline批量查询点赞状态
     *
     * @param bizIds
     * @return
     */
    @Override
    public Set<Long> getLikesStatusForRedis(List<Long> bizIds) {
        if (CollUtils.isEmpty(bizIds)) {
            return CollUtils.emptySet();
        }
        // 1.获取当前用户
        Long userId = UserContext.getUser();

        // 因为都存在redis中，所以直接从redis中获取
        // 首先是使用原始的方式
        String keyPrefix = RedisConstants.LIKE_BIZ_KEY_PREFIX;

        // 使用pipeline
        List<Object> objects = stringRedisTemplate.executePipelined((RedisCallback<Object>) (redisConnection) -> {

            // 先将命令全部放入队列中
            for (Long bizId : bizIds) {
                String key = keyPrefix + bizId;
                redisConnection.sIsMember(key.getBytes(), String.valueOf(userId).getBytes());
            }
            return null;
        });
        // 在进行批量查看

        return IntStream.range(0, objects.size())  // 创建从0到集合size的流
                .filter(i -> (Boolean) objects.get(i))  // 遍历每个元素，保留结果为true的角标i
                .mapToObj(bizIds::get)  // 用角标i取bizIds中的对应数据，就是点赞过的id
                .collect(Collectors.toSet());
    }

    /**
     * 从redis中读取点赞数和消息
     * 然后给mq发送消息
     *
     * @param bizType
     * @param maxBizSize
     */
    @Override
    public void readLikedTimesAndMessage(String bizType, int maxBizSize) {

        // 拼接key
        String key = RedisConstants.LIKE_COUNT_KEY_PREFIX + bizType;
        // 从redis的zset结构中获取 maxBizSize个点赞数最多的业务id
        // 取出来
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().popMin(key, maxBizSize);
        // 判断是否为空
        if (CollUtils.isEmpty(typedTuples)) {
            return;
        }

        // 存放mq需要发送的消息
        List<LikedTimesDTO> list = new ArrayList<>();

        // 遍历
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            // 对应的点赞数
            Double likedTimes = typedTuple.getScore();

            // 对应的业务id
            String bizId = typedTuple.getValue();

            if (StringUtils.isBlank(bizId) || likedTimes == null) {
                continue;
            }
            // 封装成LikedTimesDTO
            LikedTimesDTO likedTimesDTO = new LikedTimesDTO();
            likedTimesDTO.setLikedTimes(likedTimes.intValue());
            likedTimesDTO.setBizId(Long.valueOf(bizId));

            list.add(likedTimesDTO);
        }


        log.info("准备向mq发送消息，从redis中读取点赞数和消息, bizType:{}, maxBizSize:{}, list:{}", bizType, maxBizSize, list);

        // 发送消息
        mqHelper.send(MqConstants.Exchange.LIKE_RECORD_EXCHANGE,
                StringUtils.format(MqConstants.Key.LIKED_TIMES_KEY_TEMPLATE, bizType),
                list);
    }

    /**
     * 将redis中的点赞数据同步到数据库
     */
    @Override
    public void syncLikedDataFromRedisToDB() {
        // 1.从redis中获取所有的key
        Set<String> keys = stringRedisTemplate.keys(RedisConstants.LIKE_BIZ_KEY_PREFIX + "*");
        if (CollUtils.isEmpty(keys)) {
            // 没有数据直接返回
            return;
        }
        // 遍历每一个key将value取出来
        for (String key : keys) {
            // 获取点赞的业务id
            String bizId = key.replace(RedisConstants.LIKE_BIZ_KEY_PREFIX, "");
            // 获取点赞的用户id
            Set<String> members = stringRedisTemplate.opsForSet().members(key);
            if (CollUtils.isEmpty(members)) {
                continue;
            }

        }


    }

    /**
     * 点赞
     *
     * @param dto
     * @param userId
     * @return
     */
    private boolean liked(LikeRecordFormDTO dto, Long userId) {
        // 拼接key
        String key = RedisConstants.LIKE_BIZ_KEY_PREFIX + dto.getBizId();
        Long result = stringRedisTemplate.opsForSet().add(key, String.valueOf(userId));
        // 表明点赞成功
        return result != null && result > 0;
    }

    /**
     * 取消点赞
     *
     * @param dto
     * @param userId
     * @return
     */
    private boolean unliked(LikeRecordFormDTO dto, Long userId) {
        // 拼接key
        String key = RedisConstants.LIKE_BIZ_KEY_PREFIX + dto.getBizId();
        Long remove = stringRedisTemplate.opsForSet().remove(key, String.valueOf(userId));
        // 取消点赞成功
        return remove != null && remove > 0;
    }
}
